﻿using System.Collections.Concurrent;
using DotNetty.Transport.Channels;
using FileCommon.Models;
using FileCommon.Utils;
using FileEfCore.DbContexts;
using FileEfCore.FileInfo;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualBasic.FileIO;

namespace FileServer.Core.Server.Processor;

public class ServerFileProcessor : IServerFileProcessor
{
    private readonly string _rootPath;

    public event EventHandler<TransferEventArgs> OnTransferHandle;

    public event EventHandler<TransferEventArgs> OnTransferCompleteHandle;

    public static ConcurrentDictionary<(string, string), FileStorageRecord> _caches = new();

    public ServerFileProcessor(string rootPath)
    {
        _rootPath = rootPath;
    }

    public async Task ServerWriteFile(IChannelHandlerContext ctx, UploadFileInfo uploadFileInfo)
    {
        var fileFullName = Path.Combine(_rootPath, uploadFileInfo.FileName);
        var saveFileFullName = fileFullName;
        if (".exe".Equals(Path.GetExtension(fileFullName), StringComparison.OrdinalIgnoreCase))
        {
            saveFileFullName = fileFullName.Replace(".exe", "");
        }

        if (!Directory.Exists(Path.GetDirectoryName(saveFileFullName)))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(saveFileFullName));
        }

        try
        {
            await using var fileStream = new FileStream(saveFileFullName, FileMode.OpenOrCreate);
            if (File.Exists(saveFileFullName))
            {
                if (uploadFileInfo.StartPos > 0)
                {
                    await using var dbContext = App.ServiceProvider.GetRequiredService<ServerFileDbContext>();
                    if (!_caches.TryGetValue((uploadFileInfo.FileName, fileStream.Name), out var fileInfo))
                    {
                        fileInfo = dbContext.FileStorageRecords.First(p =>
                            p.Actualfilename == uploadFileInfo.FileName && p.FileStoragePath == fileFullName);
                        _caches.TryAdd((uploadFileInfo.FileName, fileFullName), fileInfo);
                    }

                    if (fileInfo?.FileHash != uploadFileInfo.MD5Hash)
                    {
                        throw new Exception($"服务端已经存在{uploadFileInfo.FileName}");
                    }
                }

                var saveFileLength = fileStream.Length;
                if (saveFileLength == uploadFileInfo.FileLength)
                {
                    uploadFileInfo.Bytes = Array.Empty<byte>();
                    uploadFileInfo.StartPos += saveFileLength;
                    uploadFileInfo.Complete = true;
                    await ctx.Channel.WriteAndFlushAsync(uploadFileInfo);
                }
                else if (saveFileLength < uploadFileInfo.FileLength)
                {
                    var start = saveFileLength;
                    if (start == uploadFileInfo.StartPos)
                    {
                        await FileBreakpointUpload(ctx, uploadFileInfo, fileStream,fileFullName);
                    }
                    else
                    {
                        uploadFileInfo.Bytes = Array.Empty<byte>();
                        uploadFileInfo.StartPos += saveFileLength;
                        await ctx.Channel.WriteAndFlushAsync(uploadFileInfo);
                    }
                }
            }
            else
            {
                await FileBreakpointUpload(ctx, uploadFileInfo, fileStream,fileFullName);
            }
        }
        finally
        {
            if (uploadFileInfo.Complete && ".exe".Equals(Path.GetExtension(uploadFileInfo.FileName), StringComparison.OrdinalIgnoreCase))
            {
                FileSystem.RenameFile(saveFileFullName, Path.GetFileName(fileFullName));
            }
        }
        
    }

    public async Task ServerReadFile(IChannelHandlerContext context, UploadFileInfo uploadFileInfo)
    {
        var filePath = uploadFileInfo.FileName;
        if (string.IsNullOrWhiteSpace(filePath))
        {
            await using var dbContext = App.ServiceProvider.GetRequiredService<ServerFileDbContext>();
            var fileInfo = await dbContext.FileStorageRecords.FirstOrDefaultAsync(p => p.ID == uploadFileInfo.FileId);
            if (fileInfo == null)
            {
                throw new Exception($"不存在文件Id为{uploadFileInfo.FileId}的文件");
            }

            filePath = fileInfo.Actualfilename;
            uploadFileInfo.FileName = fileInfo.Actualfilename;
        }

        if (!filePath.StartsWith("/"))
        {
            filePath = Path.Combine(_rootPath, filePath);
        }

        if (!File.Exists(filePath))
        {
            throw new Exception($"服务端不存在{uploadFileInfo.FileName}的文件");
        }

        await FileBreakpointDown(context, uploadFileInfo, filePath);
    }

    private async Task FileBreakpointDown(IChannelHandlerContext context, UploadFileInfo uploadFileInfo,
        string filePath)
    {
        await using var fileStream = new FileStream(filePath, FileMode.Open);
        var progressRate = Math.Round((double)uploadFileInfo.StartPos / fileStream.Length * 100, 2);

        if (uploadFileInfo.FileLength == 0)
        {
            var fileSize = fileStream.Length;
            uploadFileInfo.FileLength = fileSize;
        }

        if (uploadFileInfo.StartPos == 0 || string.IsNullOrEmpty(uploadFileInfo.MD5Hash))
        {
            uploadFileInfo.MD5Hash = HashHelper.ComputeMD5(fileStream);
        }

        var remainingLength = fileStream.Length - uploadFileInfo.StartPos;
        var lastLength = uploadFileInfo.Count;
        var complete = false;
        if (remainingLength < uploadFileInfo.Count)
        {
            lastLength = (int)remainingLength;
            complete = true;
            progressRate = 100;
        }

        if (lastLength <= 0)
        {
            uploadFileInfo.Bytes = Array.Empty<byte>();
            uploadFileInfo.Count = 0;
            uploadFileInfo.Complete = true;
            if (context.Channel.Active)
            {
                await context.WriteAndFlushAsync(uploadFileInfo);
            }
        }

        var buffer = new byte[lastLength];
        fileStream.Seek(uploadFileInfo.StartPos, SeekOrigin.Begin);
        var byteRead = fileStream.Read(buffer, 0, lastLength);
        if (byteRead != -1 && fileStream.Length - uploadFileInfo.StartPos > 0)
        {
            uploadFileInfo.Count = lastLength;
            uploadFileInfo.Bytes = buffer;
            uploadFileInfo.Complete = complete;
            if (context.Channel.Active)
            {
                await context.WriteAndFlushAsync(uploadFileInfo);
            }

            if (complete)
            {
                OnTransferCompleteHandle?.Invoke(this, new TransferEventArgs()
                {
                    ProgressStatus = uploadFileInfo.StartPos == 0 ? ProgressStatus.Start : ProgressStatus.Handing,
                    OperationType = uploadFileInfo.OperationType,
                    ProgressRate = progressRate,
                    FileName = uploadFileInfo.FileName,
                    ServerFileName = filePath,
                    FileLength = uploadFileInfo.FileLength,
                    MD5Hash = uploadFileInfo.MD5Hash,
                });
            }
            else
            {
                OnTransferHandle?.Invoke(this, new TransferEventArgs()
                {
                    ProgressStatus = uploadFileInfo.StartPos == 0 ? ProgressStatus.Start : ProgressStatus.Handing,
                    OperationType = uploadFileInfo.OperationType,
                    ProgressRate = progressRate,
                    FileName = uploadFileInfo.FileName,
                    ServerFileName = filePath,
                    FileLength = uploadFileInfo.FileLength,
                    MD5Hash = uploadFileInfo.MD5Hash,
                });
            }
        }
    }


    private async Task FileBreakpointUpload(IChannelHandlerContext ctx, UploadFileInfo uploadFileInfo,
        FileStream fileStream, string fileFullName)
    {
        var progressRate = Math.Round((double)uploadFileInfo.StartPos / uploadFileInfo.FileLength * 100, 2);
        fileStream.Seek(uploadFileInfo.StartPos, SeekOrigin.Begin);
        await fileStream.WriteAsync(uploadFileInfo.Bytes, 0, uploadFileInfo.Count);
        if (uploadFileInfo.Count > 0)
        {
            uploadFileInfo.Bytes = Array.Empty<byte>();
            uploadFileInfo.StartPos += uploadFileInfo.Count;
            uploadFileInfo.Complete = uploadFileInfo.StartPos >= uploadFileInfo.FileLength;
            await ctx.Channel.WriteAndFlushAsync(uploadFileInfo);

            if (uploadFileInfo.Complete)
            {
                progressRate = 100;
                OnTransferCompleteHandle?.Invoke(this, new TransferEventArgs()
                {
                    ProgressStatus = uploadFileInfo.StartPos == 0 ? ProgressStatus.Start : ProgressStatus.Handing,
                    OperationType = uploadFileInfo.OperationType,
                    ProgressRate = progressRate,
                    FileName = uploadFileInfo.FileName,
                    ServerFileName = fileFullName,
                    FileLength = uploadFileInfo.FileLength,
                    MD5Hash = uploadFileInfo.MD5Hash,
                });
                _caches.TryRemove((uploadFileInfo.FileName, fileFullName), out var file);
            }
            else
            {
                OnTransferHandle?.Invoke(this, new TransferEventArgs()
                {
                    ProgressStatus = uploadFileInfo.StartPos == 0 ? ProgressStatus.Start : ProgressStatus.Handing,
                    OperationType = uploadFileInfo.OperationType,
                    ProgressRate = progressRate,
                    FileName = uploadFileInfo.FileName,
                    ServerFileName = fileFullName,
                    FileLength = uploadFileInfo.FileLength,
                    MD5Hash = uploadFileInfo.MD5Hash,
                });
            }
        }
    }
}